/*
 * Copyright 2008-2009 Aying.Net with Fuchun.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *     http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package net.aying.util;

import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.text.ParsePosition;
import java.text.ParseException;
import java.text.SimpleDateFormat;

import javax.crypto.spec.DESKeySpec;

import net.aying.resources.Resources;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * 包含字符串处理的常用方法的工具类。包括字符串解析、加密解密、URL转换、HTML标签转换、查找替换等操作方法。
 * 
 * @author Fuchun
 * @version 1.0, 2009-6-9
 */
public class StringUtils extends Utils {

    /** 表示一个空字符串的常量（表示为：<strong>&quot;&quot;</strong>）。 */
    public static final String EMPTY = "";

    /** 表示 <strong>GBK</strong> 编码方式的字符串。 */
    public static final String GBK_ENCODING = "GBK";

    /** 表示 <strong>UTF-8</strong> 编码方式的字符串。 */
    public static final String UTF8_ENCODING = "UTF-8";

    public static final String ALGORITHM_MD5 = "MD5";

    public static final String ALGORITHM_SHA = "SHA";

    @SuppressWarnings("unused")
    private static final Log log = LogFactory.getLog(StringUtils.class);

    private static Pattern urlPatternCache = null;

    private static String urlRegexCache = null;

    private static final Object urlPatternLock = new Object();

    private static final Object urlRegexLock = new Object();

    // HEX CHARS
    @SuppressWarnings("unused")
    private static final char[] HEX_CHARS = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
            'A', 'B', 'C', 'D', 'E', 'F' };

    private static final int HIGHEST_SPECIAL = '>';

    private static char[][] specialCharactersRepresentation = new char[HIGHEST_SPECIAL + 1][];
    static {
        specialCharactersRepresentation['&'] = "&amp;".toCharArray();
        specialCharactersRepresentation['<'] = "&lt;".toCharArray();
        specialCharactersRepresentation['>'] = "&gt;".toCharArray();
        specialCharactersRepresentation['"'] = "&#034;".toCharArray();
        specialCharactersRepresentation['\''] = "&#039;".toCharArray();
    }

    /**
     * 检测给定的字符串是否为 <code>null</code> 或者为空字符串(&quot;&quot;)。
     * <p>
     * <strong>在检测前，将自动去除给定字符串的前导和后导空格</strong>
     * </p>
     * 
     * @param str 要检测的字符串。
     * @return 若给定的字符串为<code>null</code>或者为空字符串(&quot;&quot;)，则返回
     *         <code>true</code>，否则为<code>false</code>。
     */
    public static boolean isNullOrEmpty(String str) {
        return str == null || "".equals(str.trim());
    }

    /**
     * 检测给定的字符串<strong>是否不为<code>null</code>，并且长度大于0</strong>。
     * 
     * @param str 要检测的字符串。
     * @return 若给定的字符串不为<code>null</code>，并且长度大于0，则返回 <code>true</code>，否则<code>false</code>。
     */
    public static boolean hasLength(String str) {
        return (str != null && str.length() > 0);
    }

    /**
     * 检测给定的字符串<strong>是否全都是空格符</strong>。
     * 
     * @param str 要检测的字符串。
     * @return 若给定的字符串全都是空格符，则返回 <code>true</code>，否则<code>false</code>。
     */
    public static boolean isAllWhitespace(String str) {
        if (!hasLength(str)) return false;
        StringBuffer buffer = new StringBuffer(str);
        int strLen = buffer.length();
        for (int i = 0; i < strLen; i++) {
            if (!Character.isWhitespace(buffer.charAt(i))) return false;
        }
        return true;
    }

    /**
     * 去除给定字符串中的所有空白字符。
     * 
     * @param str 给定的字符串。
     * @return 新的无空白字符的字符串。
     * @see java.lang.Character#isWhitespace(char)
     */
    public static String trimAllWhitespace(String str) {
        if (!hasLength(str)) return str;
        StringBuffer buffer = new StringBuffer();
        int strLen = str.length();
        for (int i = 0; i < strLen; i++) {
            char c = str.charAt(i);
            if (!Character.isWhitespace(c)) buffer.append(c);
        }
        return buffer.toString();
    }

    /**
     * 返回给定字符串的字节长度。
     * <p>
     * 返回的字符串字节长度根据指定的 <tt>encoding</tt> 字符编码确定。 <tt>encoding</tt> 为
     * <code>null</code> 或者空字符串(&quot;&quot;)时，返回的长度等效于
     * <code>str.length()</code>。可选择的计算长度的编码方式：<strong>GBK、UTF-8</strong>。
     * <ul>
     * <li>GBK - 一个全角字符 ＝ 两个字节，一个半角字符 ＝ 一个字节</li>
     * <li>UTF-8 - 一个全角字符 ＝ 三个字节，一个半角字符 ＝ 一个字节</li>
     * </ul>
     * </p>
     * 
     * @param str 要计算长度的字符串。
     * @param encoding 要依据计算长度的字符编码方式。
     * @return 给定字符串的字节长度。
     */
    public static Integer getLength(String str, String encoding) {
        if (isNullOrEmpty(str)) return 0;
        if (isNullOrEmpty(encoding)) return str.length();
        String regexpSBCCase = "[^x00-xff]";
        if (GBK_ENCODING.equalsIgnoreCase(encoding))
            return str.trim().replaceAll(regexpSBCCase, "**").length();
        if (UTF8_ENCODING.equalsIgnoreCase(encoding))
            return str.trim().replaceAll(regexpSBCCase, "***").length();
        return str.length();
    }

    /**
     * 检测给定的字符串的每一个字符是否是数字(0-9)(<strong>该方法首先会去除给定字符串的前导和后导空格符</strong>)。
     * 
     * @param str 要检测的字符串。
     * @return 若给定的字符串的每一个字符都是数字，则返回 <code>true</code>，否则<code>false</code>。
     */
    public static boolean isAllDigit(String str) {
        if (isNullOrEmpty(str)) return false;
        StringBuffer buffer = new StringBuffer(str.trim());
        for (int i = 0, len = buffer.length(); i < len; i++) {
            if (!Character.isDigit(buffer.charAt(i))) return false;
        }
        return true;
    }

    /**
     * 检测给定的字符串的每一个字符是否是英文字母(a-zA-Z)(<strong>该方法首先会去除给定字符串的前导和后导空格符</strong>)。
     * 
     * @param str 要检测的字符串。
     * @return 若给定的字符串的每一个字符都是英文字母，则返回 <code>true</code>，否则<code>false</code>。
     */
    public static boolean isAllLetter(String str) {
        if (isNullOrEmpty(str)) return false;
        String remainingStr = str.trim().replaceAll("[a-zA-Z]", EMPTY);
        return isEmpty(remainingStr);
    }

    /**
     * 检测给定的字符串是否可解析为用 <code>java.util.Date</code> 表示的日期。
     * 
     * @param str 给定的字符串。
     * @return 若给定的字符串可以解析为用 <code>java.util.Date</code> 表示的日期，则返回
     *         <code>true</code>，否则<code>false</code>。
     */
    public static boolean isDate(String str) {
        if (isNullOrEmpty(str)) return Boolean.FALSE;
        SimpleDateFormat sdf = new SimpleDateFormat();
        try {
            return sdf.parse(str) != null;
        } catch (ParseException e) {
            log.warn(Resources.getMessage(Resources.UTIL_PARSE_DATE_ERROR));
        }
        return Boolean.FALSE;
    }

    /**
     * 检测给定的字符串是否是一个合法的Email。
     * 
     * @param str 要检测的字符串。
     * @return 若给定的字符串是一个合法的Email，则返回 <code>true</code>，否则<code>false</code>。
     */
    public static boolean isEmail(String str) {
        if (isNullOrEmpty(str)) return false;
        return Pattern.matches("^(?:\\w+\\.?)*\\w+@(?:\\w+\\.?)*[a-zA-Z]{2,3}$", str.trim());
    }

    /**
     * 获取查找 <code>URL</code> 的正则表达式的编译表示形式。
     * 
     * @return 查找 <code>URL</code> 的正则表达式的编译表示形式。
     */
    public static Pattern getFindURLPattern() {
        if (urlPatternCache == null) {
            synchronized (urlPatternLock) {
                if (urlPatternCache == null) {
                    urlPatternCache = Pattern.compile(getFindURLRegex());
                }
            }
        }
        return urlPatternCache;
    }

    /**
     * 获取查找 <code>URL</code> 的正则表达式的字符串。
     * 
     * @return 查找 <code>URL</code> 的正则表达式的字符串。
     */
    public static String getFindURLRegex() {
        if (urlRegexCache == null) {
            synchronized (urlRegexLock) {
                if (urlRegexCache == null) {
                    final String subDomain = "(?i:[a-z0-9]|[a-z0-9][-a-z0-9]*[a-z0-9])";
                    StringBuffer tdBuffer = new StringBuffer("(?x-i:");
                    tdBuffer.append("com\\.[a-z][a-z]|edu\\.[a-z][a-z]|gov\\.[a-z][a-z]|");
                    tdBuffer.append("net\\.[a-z][a-z]|org\\.[a-z][a-z]|info\\.[a-z][a-z]|");
                    tdBuffer.append("com|asia|edu|gov|biz|in(?:t|fo)|mil|net|org|");
                    tdBuffer.append("name|museum|coop|[a-z][a-z])\\b");
                    final String topDomain = tdBuffer.toString();
                    final String hostName = "(?:" + subDomain + "\\.)+" + topDomain;
                    final String notIn = ";\"'<>()\\[\\]{}\\s\\x7F-\\xFF";
                    final String notEnd = "!.,?";
                    final String anyWhere = "[^" + notIn + notEnd + "]";
                    final String embeded = "[" + notEnd + "]";
                    final String urlPath = "/" + anyWhere + "*(" + embeded + "+" + anyWhere + "+)*";
                    StringBuffer buffer = new StringBuffer("(?x:\\b");
                    buffer.append("((?:ftp|https?)://[-\\w]+(\\.\\w[-\\w]*)+|").append(hostName)
                            .append(")");
                    buffer.append("(?::\\d+)?(?:").append(urlPath).append(")?)");
                    if (log.isDebugEnabled()) log.debug("The URL regex is : " + buffer.toString());
                    urlRegexCache = buffer.toString();
                }
            }
        }
        return urlRegexCache;
    }

    /**
     * 检测给定的字符串是否是一个符合规范的 <code>URL</code> 地址。
     * 
     * @param url 给定的字符串。
     * @return 若给定的字符串是一个符合规范的 <code>URL</code> 地址，则返回 <code>true</code>，否则<code>false</code>。
     */
    public static boolean isURL(String url) {
        StringBuffer buffer = new StringBuffer("^");
        buffer.append(getFindURLRegex()).append("$");
        Matcher m = Pattern.compile(buffer.toString()).matcher(url);
        return m.find();
    }

    /**
     * 将字符串中的XML特殊字符转换成 XML 可显示的字符。
     * 
     * <pre>下列字符经过转换后的字符:
     *    &amp; --&gt; &amp;amp;
     *    &lt; --&gt; &amp;lt;
     *    &gt; --&gt; &amp;gt;
     *    &quot; --&gt; &amp;#034;
     *    &#039; --&gt; &amp;#039;
     * </pre>
     * 
     * @param str 要处理的字符串。
     * @return 处理后的字符串。
     */
    public static String escapeXml(String str) {
        int start = 0;
        int length = str.length();
        char[] arrayBuffer = str.toCharArray();
        StringBuffer escapedBuffer = null;

        for (int i = 0; i < length; i++) {
            char c = arrayBuffer[i];
            if (c <= HIGHEST_SPECIAL) {
                char[] escaped = specialCharactersRepresentation[c];
                if (escaped != null) {
                    // 创建 StringBuffer 保存转义的XML特殊字符
                    if (start == 0) {
                        escapedBuffer = new StringBuffer(length + 5);
                    }
                    // 添加未转义的部分
                    if (start < i) {
                        escapedBuffer.append(arrayBuffer, start, i - start);
                    }
                    start = i + 1;
                    // 添加转义的XML特殊字符
                    escapedBuffer.append(escaped);
                }
            }
        }
        // 没有Xml字符则无须转换
        if (start == 0) {
            return str;
        }
        // 添加余于的未转义的字符
        if (start < length) {
            escapedBuffer.append(arrayBuffer, start, length - start);
        }
        return escapedBuffer.toString();
    }

    public static void main(String[] args) throws Exception {
        // String[] strs = new String[] {
        // DigestUtils.shaHex(""), DigestUtils.shaHex("12345678"),
        // DigestUtils.shaHex("1234567890123456"),
        // DigestUtils.shaHex("12345678901234567890123456789012")
        // };
        // for(String s : strs) {
        // System.out.println(s + " 长度length = " + s.length());
        // }
        String url = "3.1415926fuzchun";
        Base64 base641 = new Base64();
        Base64 base642 = new Base64();
        Hex hex1 = new Hex();
        Hex hex2 = new Hex();
        char[] encryptBytes = hex1.encodeHex(url.getBytes());
        String encryptUrl = new String(encryptBytes);
        String decryptUrl = new String(hex2.decodeHex(encryptUrl.toCharArray()));
        System.out.println("加密后：" + encryptUrl + " 长度length = " + encryptUrl.length());
        System.out.println("解密后：" + decryptUrl);
        System.out.println(DigestUtils.md5Hex("我来自中国"));

    }
}
